package com.ly.common.running;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Stopwatch;
import com.ly.common.business.util.JsonUtil;
import com.ly.common.business.util.SpringContextUtil;
import com.ly.common.running.config.Context;
import com.ly.common.running.exception.ExceptionCode;
import com.ly.common.running.factory.BusiFlowHandleFactory;
import com.ly.common.running.handler.ExtraFactory;
import com.ly.common.running.handler.IPeHandler;
import com.ly.common.running.handler.ThreadLocalManager;
import com.ly.common.running.impl.IBaseFlowHandle;
import com.ly.common.running.model.BaseInOut;
import com.ly.common.running.model.Result;
import com.ly.common.running.model.SysHead;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class InOutConfig implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        new ExceptionCode();
    }

    public static <In extends BaseInOut, Out extends BaseInOut> Out execute(In in, String beanName, Class<Out> t,String flowType) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Assert.notNull(in, "Request must not null");
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
        log.info(String.valueOf(httpServletRequest.getRequestURL()));
        ThreadLocalManager.put("URL",httpServletRequest.getRequestURL());
        //初始化日志上下文
        initMdc(in.getSysHead());
        try {
            log.info("Execute bean[{}] request is [{}]",beanName,JsonUtil.toPrettyJsonString(in));
        }catch (Exception e){
            log.error("Can't print request log.[{}]",e);
        }

        Out out = null;
        List<Result> resultList = new ArrayList<>();

        //事务控制 手动开启事务
        DataSourceTransactionManager dataSourceTransactionManager = applicationContext.getBean(DataSourceTransactionManager.class);
        TransactionDefinition transactionDefinition = applicationContext.getBean(TransactionDefinition.class);
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

        try {
            //如果字符串里面的值为null， ""， "  "，那么返回值为false；否则为true
            Assert.hasText(in.getSysHead().getSeqNo(), "全局流水号不能为空");
            Assert.hasText(in.getSysHead().getSubSeqNo(), "子流水号不能为空");

            //上下文赋值
            Context context = new Context();
            context.setSysHead(in.getSysHead());
            Context.set(context);

            //业务前置处理
            beforeBusinessHandler(in, beanName);
            try {
                out = (Out) BusiFlowHandleFactory.getValue(flowType).execute(in,beanName,t);
                SysHead sysHead = in.getSysHead();
                resultList.add(new Result("000000", "success"));
                sysHead.setResultList(resultList);
                sysHead.setRetStatus("S");
                out.setSysHead(sysHead);
                //手动提交事务
                dataSourceTransactionManager.commit(transactionStatus);
            }catch (Exception e){
                //手动回滚事务
                dataSourceTransactionManager.rollback(transactionStatus);
                throw e;
            }
        } catch (Exception e) {
            ExceptionCode.pringExceptionByLog(e);
            SysHead sysHead = Context.get().getSysHead();
            if (sysHead.getResultList() == null) {
                resultList.add(new Result("999999", "业务执行异常"));
                sysHead.setResultList(resultList);
                sysHead.setRetStatus("F");
            }
            try {
                out = t.newInstance();
            } catch (Exception ex) {
                log.error("ERROR:{}",ex);
            }
            out.setSysHead(sysHead);
        } finally {
            try {
                //业务后置处理
                afterBusinessHandler(out, beanName);
                Context.unSet();
            }catch (Exception e){
                log.error("ERROR:{}",e);
            }finally {
                try {
                    log.info("Execute Flow[{}] execute time:{},Response is {}",beanName,stopwatch.elapsed(TimeUnit.MILLISECONDS),JsonUtil.toPrettyJsonString(out));
                } catch (JsonProcessingException e) {
                    log.error("Can't print response log.[{}]",e);
                }
            }
        }
        return out;
    }

    private static <In extends BaseInOut> void beforeBusinessHandler(In in, String beanName) {
        List<IPeHandler> peHandlerList = ExtraFactory.getEffectiveExtra(IPeHandler.class);
        peHandlerList.forEach(e -> {
            e.beforeHandle(in, beanName);
        });
    }

    private static <In extends BaseInOut> void afterBusinessHandler(In in, String beanName) {
        List<IPeHandler> peHandlerList = ExtraFactory.getEffectiveExtra(IPeHandler.class);
        peHandlerList.forEach(e -> {
            e.afterHandle(in, beanName);
        });
    }

    private static void initMdc(SysHead sysHead){
        MDC.put("seqNo",sysHead.getSeqNo());
        MDC.put("subSeqNo",sysHead.getSubSeqNo());
        MDC.put("url", String.valueOf(ThreadLocalManager.get("URL")));
    }
}
